package de.klenkse.eclipse.ftpsync.synchronizer;

import java.io.IOException;
import java.util.Set;
import java.util.TreeSet;

import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;

import de.klenkse.eclipse.ftpsync.model.FTPConfigEntry;

/**
 * This class provides a command processing service for a single synchronization
 * entry. Each entry has his own command processor. The command generator feeds
 * the command processor.
 */
public class CommandProcessor {

	/**
	 * A tree node for comparism.
	 */
	private class TreeNode implements Comparable {
		/**
		 * flag for file.
		 */
		private boolean isFile;
		/**
		 * Name of the node
		 */
		private String name;
		/**
		 * The children
		 */
		private TreeSet<TreeNode> children = new TreeSet<TreeNode>();

		/**
		 * The constructor.
		 */
		public TreeNode(String name, boolean isFile) {
			this.isFile = isFile;
			this.name = name;
		}

		/** The children. */
		public Set<TreeNode> getChildren() {
			return children;
		}

		/** Adds a child. */
		public void addChild(TreeNode child) {
			children.add(child);
		}

		/** Returns name. */
		public String getName() {
			return name;
		}

		/** Returns isFile. */
		public boolean isFile() {
			return isFile;
		}

		/**
		 * Prints the tree.
		 */
		public void print(String padding) {
			System.out.println(padding + getName());
			for (TreeNode child : getChildren()) {
				child.print(padding + " ");
			}
		}

		/** {@inheritDoc} */
		@Override
		public int compareTo(Object arg0) {
			return getName().compareTo(((TreeNode) arg0).getName());
		}
	}

	/**
	 * Ready flag.
	 */
	private boolean isReady = false;

	/**
	 * The ftp client.
	 */
	private FTPClient ftpClient = new FTPClient();

	/**
	 * The config entry for the current connection.
	 */
	private FTPConfigEntry configEntry;

	/**
	 * Constructor. Configures the {@link CommandProcessor} with a
	 * {@link FTPConfigEntry}.
	 */
	public CommandProcessor(FTPConfigEntry entry) {
		isReady = setConfigEntry(entry);
	}

	/**
	 * Sets and processes the config entry.
	 */
	public boolean setConfigEntry(FTPConfigEntry entry) {

		try {
			if (ftpClient.isConnected()) {
				ftpClient.logout();
				ftpClient.disconnect();
			}
			configEntry = entry;
			ftpClient.connect(configEntry.getServerName());
			boolean status = ftpClient.login(configEntry.getUserName(),
					configEntry.getPassword()) ? (ftpClient
					.changeWorkingDirectory(configEntry.getBasePath()) ? true
					: false) : false;

			if (status) {
				synchronizeInitially();
			}
			return status;
		} catch (IOException e) {
			return false;
		}
	}

	/**
	 * Synchronizes complete state of local and remote repository.
	 */
	private void synchronizeInitially() {
		// build two trees, one for remote, one for local, and synchronize them.
		TreeNode localTree = buildLocalTree(configEntry.getSyncBase());
		TreeNode remoteTree = buildRemoteTree("./", configEntry.getBasePath());
		compareTrees(localTree, remoteTree);
	}

	/**
	 * Compare the local and the remote tree.
	 */
	private void compareTrees(TreeNode localTree, TreeNode remoteTree) {
		// TODO: implement
		System.out.println("Local:");
		localTree.print("");
		System.out.println("Remote:");
		remoteTree.print("");
	}

	/**
	 * Builds the remote tree.
	 */
	private TreeNode buildRemoteTree(String path, String resourceName) {
		TreeNode node = new TreeNode(resourceName, false);
		try {
			for (FTPFile file : ftpClient.listFiles(path)) {
				if (file.isFile()) {
					node.addChild(new TreeNode(file.getName(), true));
				} else if (file.isDirectory()) {
					node.addChild(buildRemoteTree(path + "/" + file.getName(),
							file.getName()));
				}
			}
		} catch (IOException e) {
			// TODO This should not happen at all.
		}
		return node;
	}

	/**
	 * Builds the local tree.
	 */
	private TreeNode buildLocalTree(IResource resource) {
		if (resource instanceof IFolder) {
			TreeNode node = new TreeNode(resource.getName(), false);
			try {
				for (IResource res : ((IFolder) resource).members()) {
					TreeNode childNode = buildLocalTree(res);
					if (childNode != null) {
						node.addChild(childNode);
					}
				}
				return node;
			} catch (CoreException e) {
				// TODO: this should not happen at all...
			}
		} else if (resource instanceof IFile) {
			return new TreeNode(resource.getName(), true);
		}
		return null;
	}

	/**
	 * Executes a command. May be blocking or none-blocking.
	 */
	public void executeCommand(ResourceCommand command) {
		if (isReady()) {
			try {
				command.execute(ftpClient);
			} catch (IOException e) {
				// if this happens then something didn't work.
				e.printStackTrace();
			}
		}
		System.out.println("Added command: " + command.toString());
	}

	/**
	 * Gets the synchronization base.
	 */
	public IResource getBaseResource() {
		if (configEntry == null) {
			return null;
		}
		return configEntry.getSyncBase();
	}

	/**
	 * Tells the caller if the command processor is ready to process calls. This
	 * should be called after the initialization to look if the initialization
	 * process has been successful.
	 */
	public boolean isReady() {
		return isReady;
	}
}
